Skip to content

C 语言 sizeof 与 strlen:编译期大小与运行时长度 ​

sizeof 和 strlen 都在与"长度"打交道,初学 C 时很容易混用:拿 sizeof 去算字符串长度、或拿 strlen 去算缓冲区大小,都埋过不少雷。两者的差别不在"量什么",而在"什么时候量"——sizeof 是编译期由类型确定的运算符,strlen 是运行期逐字符扫描的库函数。本文从这个时机差异出发,讲清两者的行为边界与三个常见陷阱,适合刚开始写 C、或想复核基础概念的读者。

一个是运算符,一个是库函数 ​

sizeof 是 C 语言的关键字级运算符,以字节为单位给出操作数的大小,不需要包含任何头文件(返回类型 size_t 的定义在 <stddef.h>,不过 <stdio.h>、<string.h> 等常用头文件里也有)。它在编译期就由类型确定结果,生成的机器码里往往只剩一个常量。

strlen() 是 <string.h> 提供的库函数,从给定地址开始逐字符扫描,直到遇到结尾的 '\0' 才停,返回的字符数不含这个终止符。扫描发生在运行期,代价与字符串实际长度成正比,也依赖字符串正确终止——没有 '\0' 它会一路读越界。

一句话区分:sizeof 量的是"这块内存有多大",strlen 量的是"这段内容有多长"。

同一个 "hello",两组不同的答案 ​

用数组定义和用指针定义同一个字符串字面量,sizeof 给出的结果完全不同:

c
char a[] = "hello";   // 数组:6 个字节(5 个字符 + '\0')
char *p   = "hello";  // 指针:变量里存的是字符串首地址

printf("%zu %zu\n", sizeof(a), strlen(a));  // 6 5
printf("%zu %zu\n", sizeof(p), strlen(p));  // 8 5(64 位系统上指针 8 字节)

四个结果的由来:

  • sizeof(a) 是整个数组的字节数,包含 '\0';
  • strlen(a) 数到 '\0' 为止,不包含它;
  • sizeof(p) 只是指针变量本身的大小,与它指向的字符串无关;
  • strlen(p) 沿地址扫描到 '\0',结果与 strlen(a) 一致。

这组对照也解释了为什么"用 sizeof 算字符串长度"总是差一(多算了 '\0'),而"用 strlen 算缓冲区大小"则是类型层面的误解——它根本不反映内存块的大小。

数组传进函数就退化为指针 ​

数组一旦出现在函数形参列表里,实际类型就是指针:int f(int a[2]) 与 int f(int* a) 声明的是同一个函数。因此函数内部对形参做 sizeof,得到的是指针大小,而不是调用方数组的大小:

c
void f(int a[10]) {
    printf("%zu\n", sizeof(a));  // 8(64 位下的指针大小),不是 40
}

数组长度信息在传参边界上丢失了,需要长度就得作为独立参数传入。值得一提的是,sizeof 作用于数组本身正是数组"不退化"为指针的少数场景之一(另一个常见的例外是取地址 &),所以这个坑只出现在函数调用边界上,同一作用域内 sizeof(a) 仍然是完整数组的大小。

sizeof 不求值它的操作数 ​

sizeof 的操作数是表达式时,表达式本身并不求值——sizeof(i++) 不会让 i 自增,编译器只根据表达式类型得出大小,结果还可以用在整型常量表达式里。这既有用(可以写 sizeof(x)/sizeof(x[0]) 这类数组长度惯用法,无副作用),也容易让人误判:调试时指望 sizeof "顺带"改个值是不可能的。

例外是 C99 的变长数组(VLA):数组长度要到运行期才知道,此时 sizeof 的操作数会被求值,结果也不再是编译期常量。

打印结果用 %zu ​

sizeof 和 strlen 的返回类型都是无符号的 size_t。C99 引入了 z 长度修饰符来配合它:无符号对应 %zu,有符号版本(如 ptrdiff_t 一类)对应 %zd。打印两者的返回值用 %zu 最准确;用 %d 接 size_t 属于类型不匹配,行为未定义,64 位平台上常见的乱码输出就来自这类错配。格式串各成分的完整说明可以参考 printf 转换说明与修饰符速查。

圆括号什么时候必须 ​

sizeof 的圆括号规则取决于操作数是类型还是表达式:

  1. 操作数是类型时,圆括号必不可少:sizeof(int);
  2. 操作数是具体的量时,圆括号可有可无:sizeof name、sizeof 6.28 都合法。

尽管如此,建议所有情况都带上圆括号——与 sizeof name 相比,sizeof(name) 没有任何代价,还能避免在类型与表达式混用的场景里出错。

小结 ​

  • sizeof 是运算符,编译期由类型得出字节数;strlen 是库函数,运行期扫描到 '\0' 得出字符数;
  • 数组与指针:sizeof 对数组是整块大小(含 '\0'),对指针是指针自身大小;strlen 两者一致;
  • 数组作函数参数退化为指针,函数内的 sizeof 拿不到原数组大小,长度要单独传;
  • sizeof 不求值操作数(VLA 除外);
  • 打印 size_t 用 %zu;类型与转换说明不匹配是未定义行为。
最近更新

基于 VitePress 构建